package termWeighting;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;


import ds.BugInfo;
import type.TestTWtype;
import type.TfType;
import util.ReadSummary;

/**
 * trainDocs: document-term information of training set 
 * testDocs: document-term information of testing set
 * trainDocsByClass: document-term information of training set, divided into 4 classes
 * bugId2Class: map from bugID to class name
 * label2Index: map from class name to class index 
 * @author make
 *
 */
public class ProposedTermWeighting {
		
	public ArrayList<BugInfo> trainDocs;
	public ArrayList<BugInfo> testDocs;
	public ArrayList<ArrayList<BugInfo>> trainDocsByClass;
	
	public HashMap<String, String> bugId2Class;
	ArrayList<String> labels;
	
	private double minTF;
	private double alpha;
	private double belta;
	
	/** For Naive Bayes*/
	private ArrayList<Double> classProbability;
	private ArrayList<ArrayList<Double>> pWordClass;
	private int wordCount;
	
	/**
	 * construct function
	 * @param bugReportFile
	 * @param TfFile
	 */
	public ProposedTermWeighting( ArrayList<String> categories, HashMap<String, String> categoryMap, 
			ArrayList<BugInfo> training, ArrayList<BugInfo> testing, ArrayList<Double> pclass){	
		minTF = 1;
		alpha = 2;
		belta = 1;
				
		this.labels = new ArrayList<String>();
		labels.addAll(categories);
		
		bugId2Class = new HashMap<String, String>();
		bugId2Class.putAll(categoryMap);
		
		trainDocs = training;
		testDocs = testing;
		trainDocsByClass = new ArrayList<ArrayList<BugInfo>>();
		splitTrainDocs2Class();
		
		/** For Naive Bayes*/
		this.classProbability = pclass;
		pWordClass = new ArrayList<ArrayList<Double>>();
		for(int i = 0; i < labels.size(); i++){
			ArrayList<Double> curList = new ArrayList<Double>();
			for(int j = 0; j < wordCount; j++){
				curList.add(0.0);
			}
			pWordClass.add(curList);
		}
	}	
	
	public static void main(String[] args){
		
	}
	
	public double[] getParams(){
		double[] params = new double[3];
		params[0] = this.minTF;
		params[1] = this.alpha;
		params[2] = this.belta;
		return params;
	}
	
	public void setParams(double[] params){
		this.alpha = params[0];
		this.belta = params[1];
	}
	
	public double logAlpha(double value){
		return Math.log(value) / Math.log(this.alpha);
	}
	
	/**
	 * Initialize trainDocs and testDocs
	 * @param FileName : path of WVTool TF file
	 * @return 
	 */
	private  void buildTFmap(String FileName){	
		try {
			BufferedReader readTF = new BufferedReader(new FileReader(FileName));
			String summary = readTF.readLine();
			int[] values = ReadSummary.readTfFileSummary(summary);
			this.wordCount = values[0];
			int trainBugCount = values[1];
			int testBugCount = values[2];
			
			for(int i = 0;  i < trainBugCount + testBugCount; i++){				
				String[] words = readTF.readLine().split(" ");
				String bugId = words[0].substring(0, words[0].length()-1);
				
				BugInfo CurrentDoc = new BugInfo();
				CurrentDoc.bugID = bugId;
				CurrentDoc.label= bugId2Class.get(bugId);			
				for(int j=1; j<words.length; j++){
					int wordIndex = Integer.parseInt(words[j].split(":")[0]);
					Double wordTF = Double.parseDouble(words[j].split(":")[1]);
					if(wordTF < minTF)
						minTF = wordTF;
					CurrentDoc.wordId2TF.put(wordIndex, wordTF);
				}
				for(int count = 0; count < wordCount; count++)
					CurrentDoc.termWeight.add(0.0);
				
				if(i < trainBugCount)
					trainDocs.add(CurrentDoc);
				else
					testDocs.add(CurrentDoc);
			}
			readTF.close();
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}

	/**
	 * Initialize trainDocsByClass
	 * @param trainDocs
	 * @return
	 */
	private void splitTrainDocs2Class(){
		 int i = 0;		 
		 for(i = 0; i < this.labels.size(); i++)
			 trainDocsByClass.add(new ArrayList<BugInfo>());
		 
		 for(i = 0; i < trainDocs.size(); i++){
			 BugInfo currentDoc = trainDocs.get(i);
			 trainDocsByClass.get(this.labels.indexOf(currentDoc.label)).add(currentDoc);
		 }
	}
	 
	/**
	 * TW is short for term weight
	 * @param curDoc
	 * @param curClassIndex
	 * @return
	 */
	private BugInfo calculateTM(BugInfo curDoc, int curClassIndex, TfType tfType){
		double pPositive=0, pNegative=0, positiveNumerator=0,
				positiveDenominator=0, negativeNumerator=0, negativeDenominator = 0;
		
		ArrayList<BugInfo> positiveDocSet = trainDocsByClass.get(curClassIndex);
		ArrayList<BugInfo> negativeDocSet = new ArrayList<BugInfo>();
		for(int i = 0; i < trainDocsByClass.size(); i++){
			if(i != curClassIndex)
				negativeDocSet.addAll(trainDocsByClass.get(i));
		}
		
		Iterator<Entry<Integer, Double>>  iter = curDoc.wordId2TF.entrySet().iterator();
		while(iter.hasNext()){		
			Entry<Integer, Double> entry = iter.next();
			Integer wordIndex = entry.getKey();
			
			for(BugInfo positiveDoc : positiveDocSet){	
				if(positiveDoc.wordId2TF.containsKey(wordIndex))
					positiveNumerator += positiveDoc.wordId2TF.get(wordIndex);
				
				Iterator<Entry<Integer, Double>>  TFiter = positiveDoc.wordId2TF.entrySet().iterator();
				while(TFiter.hasNext())
					positiveDenominator  += TFiter.next().getValue();
			}
			pPositive = positiveNumerator / positiveDenominator;
			
			/** For Naive Bayes*/
			double NBprobility = (1 + positiveNumerator) / (wordCount + positiveDenominator);
			this.pWordClass.get(curClassIndex).set(wordIndex, NBprobility);
			
			for(BugInfo negativeDoc : negativeDocSet){
				if(negativeDoc.wordId2TF.containsKey(wordIndex))
					negativeNumerator += negativeDoc.wordId2TF.get(wordIndex);
				
				Iterator<Entry<Integer, Double>>  TFiterN = negativeDoc.wordId2TF.entrySet().iterator();
				while(TFiterN.hasNext())
					negativeDenominator += TFiterN.next().getValue();
			}
			pNegative = negativeNumerator / negativeDenominator;
			
			double weightForCurrentWord = 0;
			if(tfType == TfType.normolizedTF)
				weightForCurrentWord = (logAlpha(entry.getValue() + this.alpha))
					* (logAlpha((pPositive/pNegative) + this.alpha));
			else if(tfType == TfType.pureTF)
				weightForCurrentWord = (logAlpha(entry.getValue()) + 1) *
						(logAlpha((pPositive/pNegative) + this.alpha));
			
			curDoc.termWeight.set(wordIndex, weightForCurrentWord);
		}
		return curDoc;
	}
	
	public ArrayList<BugInfo> caculateTermWeightForTrain(TfType tfType){
		ArrayList<BugInfo> trainDocsHasTM = new ArrayList<BugInfo>();
		
		for(BugInfo curDoc : this.trainDocs){
			int curClassIndex = this.labels.indexOf(curDoc.label);
			curDoc = calculateTM(curDoc, curClassIndex, tfType);
			trainDocsHasTM.add(curDoc);
		}
		return trainDocsHasTM;
	}
	
	private BugInfo selectRightTM(BugInfo[] docByClass, TestTWtype strategy){	
		if(strategy == TestTWtype.W_Max)
			return W_Max(docByClass);
		else if(strategy == TestTWtype.D_Max)
			return D_Max(docByClass)	;
		else if(strategy == TestTWtype.D_TMax)
			return D_TMax(docByClass);
		else
			return null;
	}
	
	private BugInfo W_Max(BugInfo[] docByClass){
		BugInfo result = new BugInfo(docByClass[0]);

		for(int i = 0; i < result.termWeight.size(); i++){
			double max = 0;
			for(BugInfo curDoc : docByClass){
				max = curDoc.termWeight.get(i) > max ? curDoc.termWeight.get(i) : max;	
			}
			result.termWeight.set(i, max);
		}
		return result;
	}
	
	private BugInfo D_Max(BugInfo[] docByClass){
		double max=0;
		int index = 0;
		
		double[] sum = new double[docByClass.length];
		for(int i = 0; i < docByClass.length; i++){
			sum[i] = 0;
			for(Double weight : docByClass[i].termWeight){
				sum[i] += weight;
			}
		}
		
		for(int j = 0; j < sum.length; j++){
			if(sum[j] > max){
				max = sum[j];
				index = j;
			}
		}
		return new BugInfo(docByClass[index]);
	}
	
	private BugInfo D_TMax(BugInfo[] docByClass){
		BugInfo result = new BugInfo(docByClass[0]);
		int i = 0, index = 0, subIndex = 0;
		double max = 0;
		
		double[] sum = new double[docByClass.length];
		for(i = 0; i < docByClass.length; i++){
			sum[i] = 0;
			for(Double weight : docByClass[i].termWeight){
				sum[i] += weight;
			}
		}
		
		for(i = 0; i < sum.length; i++){
			if(sum[i] >= max){
				max = sum[i];
				subIndex = index;
				index = i;
			}
		}
		
		for(i = 0; i < result.termWeight.size(); i++){
			double weight1 = docByClass[index].termWeight.get(i);
			double weight2 = docByClass[subIndex].termWeight.get(i);
			result.termWeight.set(i, weight1>weight2? weight1 : weight2);
		}
		
		return result;
	}
	
	public ArrayList<BugInfo> calculateTermWeightForTest(TestTWtype strategy, TfType tfType){
		ArrayList<BugInfo> testDocsHasTM = new ArrayList<BugInfo>();
		
		for(BugInfo curDoc : this.testDocs){
			int classCount = this.labels.size();
			BugInfo[] docByClass = new BugInfo[classCount];
			
			for(int curClassIndex = 0; curClassIndex < this.labels.size(); curClassIndex++){
				docByClass[curClassIndex] = new BugInfo(calculateTM(curDoc, curClassIndex, tfType));
			}
			
			testDocsHasTM.add(selectRightTM(docByClass, strategy));
		}
		return testDocsHasTM;
	}
	
	public void predictUseNB(String NBfilename){	
		try {
			FileWriter fw = new FileWriter(NBfilename);
			
			for(BugInfo curDoc : this.testDocs){
				double[] probilityOfLabels = new double[this.labels.size()];
				int rightIndex = this.labels.indexOf(curDoc.label);
				
				//double sumProbilityForCurrentDoc = 0.0;
				for(int i = 0; i < this.labels.size(); i++){
					double probilityOfCurrentLabel = this.classProbability.get(i);
					
					Iterator<Entry<Integer, Double>>  iter = curDoc.wordId2TF.entrySet().iterator();					
					while(iter.hasNext()){		
						Entry<Integer, Double> entry = iter.next();
						Integer wordIndex = entry.getKey();
						probilityOfCurrentLabel *= this.pWordClass.get(i).get(wordIndex);
					}
					
					probilityOfLabels[i] = probilityOfCurrentLabel;
					//sumProbilityForCurrentDoc += probilityOfCurrentLabel;
				}
				
				int maxIndex = 0;
				double max = 0.0;
				for(int curIndex = 0; curIndex < probilityOfLabels.length; curIndex++){
					if(probilityOfLabels[curIndex] > max){
						max = probilityOfLabels[curIndex];
						maxIndex = curIndex;
					}
				}
				
				fw.write(rightIndex + " " + maxIndex + "\n");
			}
			System.out.println("NB predict completed!");
			fw.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}
